<!DOCTYPE html><html lang="zh-CN" data-theme="light"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"><title>Zookeeper面经 | 无人深空</title><meta name="keywords" content="Zookeeper"><meta name="author" content="zcm"><meta name="copyright" content="zcm"><meta name="format-detection" content="telephone=no"><meta name="theme-color" content="#ffffff"><meta name="description" content="Zookeeper 面经1.介绍下 Zookeeper 简介  Zookeeper 是一个开源的分布式的，为分布式框架提供协调服务的 Apache 项目 工作机制如下图    优点  分布式协调过程简单：zk 节点之间的协调过程非常简单 同步：zk 高度同步，这意味着服务器进程之间既存在互斥又存在合作，同步有助于Apache HBase进行配置管理 序列化：根据具体规则，zk 对数据进行编码。 另">
<meta property="og:type" content="article">
<meta property="og:title" content="Zookeeper面经">
<meta property="og:url" content="https://gitee.com/zcmmmm/zcmmmm/2022/12/10/zookeeper%E9%9D%A2%E7%BB%8F/index.html">
<meta property="og:site_name" content="无人深空">
<meta property="og:description" content="Zookeeper 面经1.介绍下 Zookeeper 简介  Zookeeper 是一个开源的分布式的，为分布式框架提供协调服务的 Apache 项目 工作机制如下图    优点  分布式协调过程简单：zk 节点之间的协调过程非常简单 同步：zk 高度同步，这意味着服务器进程之间既存在互斥又存在合作，同步有助于Apache HBase进行配置管理 序列化：根据具体规则，zk 对数据进行编码。 另">
<meta property="og:locale" content="zh_CN">
<meta property="og:image" content="https://study-record-images.oss-cn-beijing.aliyuncs.com/Zookeeper/Zookeeper.png">
<meta property="article:published_time" content="2022-12-10T05:47:38.000Z">
<meta property="article:modified_time" content="2023-05-02T02:12:30.134Z">
<meta property="article:author" content="zcm">
<meta property="article:tag" content="Zookeeper">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="https://study-record-images.oss-cn-beijing.aliyuncs.com/Zookeeper/Zookeeper.png"><link rel="shortcut icon" href="/img/favicon.png"><link rel="canonical" href="https://gitee.com/zcmmmm/zcmmmm/2022/12/10/zookeeper%E9%9D%A2%E7%BB%8F/"><link rel="preconnect" href="//cdn.jsdelivr.net"/><link rel="preconnect" href="//busuanzi.ibruce.info"/><link rel="stylesheet" href="/css/index.css"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free/css/all.min.css" media="print" onload="this.media='all'"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/node-snackbar/dist/snackbar.min.css" media="print" onload="this.media='all'"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fancyapps/ui/dist/fancybox.min.css" media="print" onload="this.media='all'"><script>const GLOBAL_CONFIG = { 
  root: '/',
  algolia: undefined,
  localSearch: {"path":"/search.xml","preload":false,"languages":{"hits_empty":"找不到您查询的内容：${query}"}},
  translate: undefined,
  noticeOutdate: undefined,
  highlight: {"plugin":"highlighjs","highlightCopy":true,"highlightLang":true,"highlightHeightLimit":200},
  copy: {
    success: '复制成功',
    error: '复制错误',
    noSupport: '浏览器不支持'
  },
  relativeDate: {
    homepage: false,
    post: false
  },
  runtime: '天',
  date_suffix: {
    just: '刚刚',
    min: '分钟前',
    hour: '小时前',
    day: '天前',
    month: '个月前'
  },
  copyright: undefined,
  lightbox: 'fancybox',
  Snackbar: {"chs_to_cht":"你已切换为繁体","cht_to_chs":"你已切换为简体","day_to_night":"你已切换为深色模式","night_to_day":"你已切换为浅色模式","bgLight":"#49b1f5","bgDark":"#1f1f1f","position":"bottom-left"},
  source: {
    justifiedGallery: {
      js: 'https://cdn.jsdelivr.net/npm/flickr-justified-gallery/dist/fjGallery.min.js',
      css: 'https://cdn.jsdelivr.net/npm/flickr-justified-gallery/dist/fjGallery.min.css'
    }
  },
  isPhotoFigcaption: false,
  islazyload: false,
  isAnchor: false
}</script><script id="config-diff">var GLOBAL_CONFIG_SITE = {
  title: 'Zookeeper面经',
  isPost: true,
  isHome: false,
  isHighlightShrink: false,
  isToc: true,
  postUpdate: '2023-05-02 10:12:30'
}</script><noscript><style type="text/css">
  #nav {
    opacity: 1
  }
  .justified-gallery img {
    opacity: 1
  }

  #recent-posts time,
  #post-meta time {
    display: inline !important
  }
</style></noscript><script>(win=>{
    win.saveToLocal = {
      set: function setWithExpiry(key, value, ttl) {
        if (ttl === 0) return
        const now = new Date()
        const expiryDay = ttl * 86400000
        const item = {
          value: value,
          expiry: now.getTime() + expiryDay,
        }
        localStorage.setItem(key, JSON.stringify(item))
      },

      get: function getWithExpiry(key) {
        const itemStr = localStorage.getItem(key)

        if (!itemStr) {
          return undefined
        }
        const item = JSON.parse(itemStr)
        const now = new Date()

        if (now.getTime() > item.expiry) {
          localStorage.removeItem(key)
          return undefined
        }
        return item.value
      }
    }
  
    win.getScript = url => new Promise((resolve, reject) => {
      const script = document.createElement('script')
      script.src = url
      script.async = true
      script.onerror = reject
      script.onload = script.onreadystatechange = function() {
        const loadState = this.readyState
        if (loadState && loadState !== 'loaded' && loadState !== 'complete') return
        script.onload = script.onreadystatechange = null
        resolve()
      }
      document.head.appendChild(script)
    })
  
      win.activateDarkMode = function () {
        document.documentElement.setAttribute('data-theme', 'dark')
        if (document.querySelector('meta[name="theme-color"]') !== null) {
          document.querySelector('meta[name="theme-color"]').setAttribute('content', '#0d0d0d')
        }
      }
      win.activateLightMode = function () {
        document.documentElement.setAttribute('data-theme', 'light')
        if (document.querySelector('meta[name="theme-color"]') !== null) {
          document.querySelector('meta[name="theme-color"]').setAttribute('content', '#ffffff')
        }
      }
      const t = saveToLocal.get('theme')
    
          if (t === 'dark') activateDarkMode()
          else if (t === 'light') activateLightMode()
        
      const asideStatus = saveToLocal.get('aside-status')
      if (asideStatus !== undefined) {
        if (asideStatus === 'hide') {
          document.documentElement.classList.add('hide-aside')
        } else {
          document.documentElement.classList.remove('hide-aside')
        }
      }
    
    const detectApple = () => {
      if(/iPad|iPhone|iPod|Macintosh/.test(navigator.userAgent)){
        document.documentElement.classList.add('apple')
      }
    }
    detectApple()
    })(window)</script><link rel="stylesheet" href="/css/categories.css?v1"><link rel="stylesheet" href="/css/navigation.css?v1"><link rel="stylesheet" href="/css/custom.css?v1"><meta name="generator" content="Hexo 6.3.0"></head><body><div id="sidebar"><div id="menu-mask"></div><div id="sidebar-menus"><div class="avatar-img is-center"><img src="https://s2.loli.net/2022/09/22/36LN4kcjYOd9HT8.jpg" onerror="onerror=null;src='/img/friend_404.gif'" alt="avatar"/></div><div class="sidebar-site-data site-data is-center"><a href="/archives/"><div class="headline">文章</div><div class="length-num">43</div></a><a href="/tags/"><div class="headline">标签</div><div class="length-num">25</div></a><a href="/categories/"><div class="headline">分类</div><div class="length-num">11</div></a></div><hr/><div class="menus_items"><div class="menus_item"><a class="site-page" href="/"><i class="fa-fw fas fa-home"></i><span> 首页</span></a></div><div class="menus_item"><a class="site-page" href="/archives/"><i class="fa-fw fas fa-archive"></i><span> 时间轴</span></a></div><div class="menus_item"><a class="site-page" href="/tags/"><i class="fa-fw fas fa-tags"></i><span> 标签</span></a></div><div class="menus_item"><a class="site-page" href="/categories/"><i class="fa-fw fas fa-folder-open"></i><span> 分类</span></a></div></div></div></div><div class="post" id="body-wrap"><header class="not-top-img" id="page-header"><nav id="nav"><span id="blog_name"><a id="site-name" href="/">无人深空</a></span><div id="menus"></div><div class="menus_items"><div class="menus_item"><a class="site-page" href="/"><i class="fa-fw fas fa-home"></i><span> 首页</span></a></div><div class="menus_item"><a class="site-page" href="/archives/"><i class="fa-fw fas fa-archive"></i><span> 时间轴</span></a></div><div class="menus_item"><a class="site-page" href="/tags/"><i class="fa-fw fas fa-tags"></i><span> 标签</span></a></div><div class="menus_item"><a class="site-page" href="/categories/"><i class="fa-fw fas fa-folder-open"></i><span> 分类</span></a></div></div><div id="nav-right"><div id="search-button"><a class="site-page social-icon search"><i class="fas fa-search fa-fw"></i></a></div><div id="toggle-menu"><a class="site-page"><i class="fas fa-bars fa-fw"></i></a></div></div></nav></header><main class="layout" id="content-inner"><div id="post"><div id="post-info"><h1 class="post-title">Zookeeper面经</h1><div id="post-meta"><div class="meta-firstline"><span class="post-meta-date"><i class="far fa-calendar-alt fa-fw post-meta-icon"></i><span class="post-meta-label">发表于</span><time class="post-meta-date-created" datetime="2022-12-10T05:47:38.000Z" title="发表于 2022-12-10 13:47:38">2022-12-10</time><span class="post-meta-separator">|</span><i class="fas fa-history fa-fw post-meta-icon"></i><span class="post-meta-label">更新于</span><time class="post-meta-date-updated" datetime="2023-05-02T02:12:30.134Z" title="更新于 2023-05-02 10:12:30">2023-05-02</time></span><span class="post-meta-categories"><span class="post-meta-separator">|</span><i class="fas fa-inbox fa-fw post-meta-icon"></i><a class="post-meta-categories" href="/categories/%E9%9D%A2%E7%BB%8F/">面经</a><i class="fas fa-angle-right post-meta-separator"></i><i class="fas fa-inbox fa-fw post-meta-icon"></i><a class="post-meta-categories" href="/categories/%E5%A4%A7%E6%95%B0%E6%8D%AE/">大数据</a></span></div><div class="meta-secondline"><span class="post-meta-separator">|</span><span class="post-meta-wordcount"><i class="far fa-file-word fa-fw post-meta-icon"></i><span class="post-meta-label">字数总计:</span><span class="word-count">10.8k</span><span class="post-meta-separator">|</span><i class="far fa-clock fa-fw post-meta-icon"></i><span class="post-meta-label">阅读时长:</span><span>33分钟</span></span><span class="post-meta-separator">|</span><span class="post-meta-pv-cv" id="" data-flag-title="Zookeeper面经"><i class="far fa-eye fa-fw post-meta-icon"></i><span class="post-meta-label">阅读量:</span><span id="busuanzi_value_page_pv"><i class="fa-solid fa-spinner fa-spin"></i></span></span></div></div></div><article class="post-content" id="article-container"><h2 id="Zookeeper-面经"><a href="#Zookeeper-面经" class="headerlink" title="Zookeeper 面经"></a>Zookeeper 面经</h2><h4 id="1-介绍下-Zookeeper"><a href="#1-介绍下-Zookeeper" class="headerlink" title="1.介绍下 Zookeeper"></a>1.介绍下 Zookeeper</h4><ul>
<li><p>简介</p>
<blockquote>
<p>Zookeeper 是一个开源的分布式的，为分布式框架提供协调服务的 Apache 项目</p>
<p>工作机制如下图</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Zookeeper/%E5%B7%A5%E4%BD%9C%E6%9C%BA%E5%88%B6.png" alt="工作机制"></p>
</li>
<li><p>优点</p>
<ul>
<li>分布式协调过程简单：zk 节点之间的协调过程非常简单</li>
<li>同步：zk 高度同步，这意味着服务器进程之间既存在互斥又存在合作，同步有助于Apache HBase进行配置管理</li>
<li>序列化：根据具体规则，zk 对数据进行编码。 另外它还可确保应用程序始终如一地运行。 但是在 MapReduce 中，使用序列化来协调队列以执行正在运行的线程</li>
<li>消息有序性：zk 中的消息是有序的。 为了实现更高级别的抽象，需要有序性</li>
<li>可扩展性：可以通过部署更多机器来加强zk的性能</li>
<li>实时性：zk 保证在一定时间段内，客户端最终一定能从服务器上读到最新的数据状态</li>
<li>原子性：zk 只有两种情况，要么全部成功，要么全部失败，没有中间状态的情况</li>
</ul>
</li>
<li><p>缺点 &#x2F; 局限性</p>
<ul>
<li><p>zookeeper 不是为高可用设计的</p>
<blockquote>
<p>由于要跨机房容灾，很多系统实际上是需要跨机房部署的。出于性价比的考虑，通常会让多个机房同时工作，而不会搭建 N 倍冗余。也就是说单个机房肯定撑不住全流量。由于 zookeeper 集群只能有一个 master，因此一旦机房之间出现故障，zookeeper master 就只能照顾一个机房，其他机房由于没有 master，都只能停止工作。于是所有的流量集中到有 master 的那个机房，于是系统 crash</p>
<p>即使同一个机房里面，由于网段不同，在调整机房交换机的时候偶尔也会发生网段隔离的情况。实际上机房每个月基本都会发生短暂的网络隔离之类的子网段调整。在那个时刻 zookeeper 处于不可用状态。如果整个业务系统基于 zookeeper，则系统的可用性将非常脆弱</p>
<p>由于 zookeeper 对于网络隔离的极度敏感，导致 zookeeper 对于网络的风吹草动都会做出激烈反应。这使得 zookeeper 的不可用时间比较多，不能让 zookeeper 的不可用，变为系统的不可用</p>
</blockquote>
</li>
<li><p>zookeeper 选举过程很慢</p>
<blockquote>
<p>网络实际上常常会出现隔离等不完整状态的，而 zookeeper 对那种情况非常敏感</p>
<p>一旦出现网络隔离，zookeeper 就会发起选举流程</p>
</blockquote>
</li>
<li><p>zookeeper 的选举流程非常耗时间</p>
<blockquote>
<p>通常耗时 30~120 秒，期间 zookeeper 由于没有 master，都是不可用的</p>
<p>对于网络里面偶尔出现的，比如半秒一秒的网络隔离，zookeeper 会由于选举过程，而把不可用时间放大几十倍</p>
</blockquote>
</li>
<li><p>zookeeper 性能是有限的</p>
<blockquote>
<p>典型的 zookeeper 的 tps 大概是一万多，无法覆盖系统每天动辄几十亿的调用</p>
<p>因此每次请求都去 zookeeper 获取业务系统 master 是不可能的</p>
<p>因此 zookeeper 的 client 必须自己缓存业务系统的 master 地址</p>
<p>因此 zookeeper 提供的强一致性实际上是不可用的。如果我们需要强一致性，还需要其他机制来进行保证。比如：用脚本自动将系统 old master 给 Kill 掉</p>
</blockquote>
</li>
<li><p>zookeeper 权限薄弱</p>
<blockquote>
<p>zookeepr 权限系统非常薄弱</p>
<p>在大型分布式系统中，使用 zookeeper 必须再额外开发一套权限系统</p>
<p>额外的权限系统不但增加了系统的复杂度和维护成本，而且降低了系统的性能</p>
</blockquote>
</li>
</ul>
</li>
<li><p>作用 &#x2F; 应用场景</p>
<blockquote>
<p>提供的服务包括</p>
<p><strong>统一命名服务、统一配置管理、统一集群管理、服务器节点动态上下线、软负载均衡</strong>等</p>
</blockquote>
</li>
</ul>
<h4 id="2-Zookeeper-的选举策略"><a href="#2-Zookeeper-的选举策略" class="headerlink" title="2.Zookeeper 的选举策略"></a>2.Zookeeper 的选举策略</h4><ul>
<li><p>服务器状态</p>
<ul>
<li>LOOKING：寻找 Leader 状态。当服务器处于该状态时，它会认为当前集群中没有 Leader，因此需要进入 Leader 选举状态</li>
<li>FOLLOWING：跟随者状态。表明当前服务器角色是 Follower</li>
<li>LEADING：领导者状态。表明当前服务器角色是 Leader</li>
<li>OBSERVING：观察者状态。表明当前服务器角色是 Observer</li>
</ul>
</li>
<li><p>投票数据结构</p>
<blockquote>
<p>每个投票中包含了两个最基本的信息，所推举服务器的 SID 和 ZXID，投票（Vote）在 Zookeeper 中包含字段如下</p>
</blockquote>
<ul>
<li>id：被推举的 Leader 的 SID</li>
<li>zxid：被推举的 Leader 事务 ID</li>
<li>electionEpoch：逻辑时钟，用来判断多个投票是否在同一轮选举周期中，该值在服务端是一个自增序列，每次进入新一轮的投票后，都会对该值进行加 1 操作</li>
<li>peerEpoch：被推举的 Leader 的 epoch</li>
<li>state：当前服务器的状态</li>
</ul>
</li>
<li><p>Leader 选举概述</p>
<blockquote>
<p>当 Zookeeper 集群中的<strong>一台服务器</strong>出现以下两种情况之一时，需要进入 Leader 选举</p>
</blockquote>
<ul>
<li>服务器初始化启动</li>
<li>服务器运行期间无法和 Leader 保持连接</li>
</ul>
<blockquote>
<p>选举机制</p>
</blockquote>
<ul>
<li><p><strong>服务端启动时选举过程</strong></p>
<ul>
<li><p>每个 server 发起一个投票</p>
<blockquote>
<p>由于当前是刚启动状态，因此每一个服务实例都会默认把自己作为 Leader 服务器来发起投票，而每一个投票中包含了最基本的选举所需要的元素，例如 myid 和 ZXID，将这两个按照选票的方式表示，例如 myid 为 1，ZXID 为 0，将会表示为 (1,0)，由于每台服务端实例优先都是按照自己是 Leader 发起投票，那么 server1 默认生成的选票就是 (1,0)，server2 的选票则是 (2,0)，以此类推</p>
</blockquote>
</li>
<li><p>接受到其他服务端实例发来的选票</p>
<blockquote>
<p>每台服务端实例都会收到其他服务端实例发来的选票信息，当收到选票后，就会开始校验处理选票的流程</p>
</blockquote>
</li>
<li><p>校验处理选票</p>
<blockquote>
<p>其他服务实例发来的选票，要经过一系列验证，比如是不是本轮投票的选票，是否来自于 Looking 状态的实例发来的选票，经过校验后，就会和当前实例的选票信息进行 pk 比较</p>
<p>比较优先级：<code>EPOCH &gt; ZXID &gt; SID(myid)</code></p>
</blockquote>
</li>
<li><p>统计每一次选票</p>
<blockquote>
<p>每一次投票以后，都会统计所有的投票，判断是否有过半的实例接受到了相同的选票信息，对与当前 server1 和 server2 来说，必须这两个实例的选票都一样才可以算是完成了选举流程，而如果是单数的实例的话，只需要达到（实例数 + 1）&#x2F; 2 的服务端实例接受到一样的选票即可</p>
<p>而经过上面的流程以后，只要 server1 比较完选票，也发出了 (2,0) 的选票信息，即可完成选举</p>
</blockquote>
</li>
<li><p>同步服务端实例状态</p>
<blockquote>
<p>一旦选举完成，选出了 Leader 实例，每个服务实例都会更新自身的状态，如果是 Follower，就会变为 <strong>FOLLOWER</strong>，如果是 Leader 则会变成 <strong>LEADING</strong> 状态</p>
</blockquote>
</li>
</ul>
</li>
<li><p><strong>服务器运行期间无法和 Leader 保持连接时选举过程</strong></p>
<blockquote>
<p>一旦 Leader 无法响应或者是宕机了，Zookeeper 集群将无法对外进行服务，而是进行新一轮的 Leader 选举，而这个选举的过程与初始化启动集群的选举过程大体上是差不多的，但是有区别的是这个时候每个机器将要从自身的运行状态切换到选举状态</p>
</blockquote>
<ul>
<li><p>更新自身状态</p>
<blockquote>
<p>当 Leader 实例挂了以后，剩下的所有 Follower 实例都会将自身的服务状态变更为 LOOKING，然后进行 Leader 选举流程</p>
</blockquote>
</li>
<li><p>一样的选举流程</p>
<blockquote>
<p>当 Leader 实例挂了以后，剩下的所有 Follower 实例都会将自身的服务状态变更为 LOOKING，然后进行 Leader 选举流程</p>
</blockquote>
</li>
</ul>
</li>
</ul>
<blockquote>
<p>5 台 zookeeper 节点刚启动时选举流程举例如下：</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Zookeeper/%E9%80%89%E4%B8%BE1.png" alt="选举1"></p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Zookeeper/%E9%80%89%E4%B8%BE2.png" alt="选举2"></p>
</li>
<li><p>介绍下 Zookeeper 选举算法</p>
<blockquote>
<p>在 zookeeper 中，提供了三种 Leader 选举的算法</p>
</blockquote>
<ul>
<li><strong>LeaderElection</strong></li>
<li><strong>UDP 版本的 FastLeaderElection</strong></li>
<li><strong>TCP 版本的 FastLeaderElection</strong></li>
</ul>
<blockquote>
<p>可以在 <code>zoo.cfg</code> 配置文件中的 <code>electionAlg</code> 属性来指定，这三种选举算法分别对应值为 0-3</p>
</blockquote>
<ul>
<li>0 为 LeaderElection算法，使用的是 UDP 协议实现</li>
<li>1 代表 UDP 版本的 FastLeaderElection 算法，这种算法是<strong>非授权模式</strong></li>
<li>2 代表的也是 UDP 版本的 FastLeaderElection 算法，不过这种使用的是<strong>授权模式</strong>（即 <code>AuthFastLeaderElection</code>）</li>
<li>3 代表是 TCP 协议实现的 FastLeaderElection 算法</li>
</ul>
<blockquote>
<p>从 Zookeeper3.4.X 版本开始，Zookeeper 官方已经废弃了 UDP 协议实现的 0-2 这三种 Leader 选举算法，仅仅保留了 3 这一种 TCP 协议实现的 <strong>FastLeaderElection</strong> 算法</p>
<p><strong>上述介绍选举的大致流程即为 TCP 协议实现的 FastLeaderElection 算法</strong></p>
</blockquote>
</li>
<li><p>Leader、Follower、Observer 的区别？</p>
<blockquote>
<p>Leader 负责读写，Follower 只负责读</p>
<p>Follower 和 Observer 归类为 Learner</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Zookeeper/Zookpeeper%E8%8A%82%E7%82%B9%E8%A7%92%E8%89%B2.jpg" alt="Zookpeeper节点角色"></p>
<ul>
<li><p>Leader</p>
<blockquote>
<p>是 zookeeper 集群的中心，负责协调集群中的其他节点。从性能的角度考虑，leader 可以选择不接受客户端的连接</p>
<p>主要作用如下</p>
</blockquote>
<ul>
<li><p>发起与提交写请求</p>
<blockquote>
<p>所有的跟随者 Follower 与观察者 Observer 节点的写请求都会转交给领导者 Leader 执行</p>
<p>Leader 接受到一个写请求后，首先会发送给所有的 Follower，统计 Follower 写入成功的数量。当有超过半数的 Follower 写入成功后，Leader 就会认为这个写请求提交成功，通知所有的 Follower commit 这个写操作，保证事后哪怕是集群崩溃恢复或者重启，这个写操作也不会丢失</p>
</blockquote>
</li>
<li><p>与 Learner 保持心跳</p>
</li>
<li><p>崩溃恢复时负责恢复数据以及同步数据到 Learner</p>
</li>
</ul>
</li>
<li><p>Follower</p>
<blockquote>
<p>Follower 在集群中有多个，主要作用如下：</p>
</blockquote>
<ul>
<li>与 Leader 保持心跳连接</li>
<li>当 Leader 挂了的时候，经过投票后成为新的 Leader。Leader 的重新选举是由 Follower 们内部投票决定的</li>
<li>向 Leader 发送消息与请求</li>
<li>处理 Leader 发来的消息与请求</li>
</ul>
</li>
<li><p>Observer</p>
<blockquote>
<p>Observer 是 zookeeper 集群中最边缘的存在。Observer 的主要作用是提高 zookeeper 集群的读性能</p>
<p>zookeeper 的一个写操作是要经过半数以上的 Follower 确认才能够写成功的，当 zookeeper 集群中的节点越多时，zookeeper 的写性能就越差</p>
<p>为了在提高 zookeeper 读性能（也就是支持更多的客户端连接）的同时又不影响 zookeeper 的写性能，zookeeper 集群设置了 Observer 角色</p>
<p>Zookeeper需保证高可用和强一致性；</p>
<p>当集群节点数目逐渐增大为了支持更多的客户端，需要增加更多 Server，然而 Server 增多，投票阶段延迟增大，影响性能。为了权衡伸缩性和高吞吐率，引入 Observer</p>
<p>主要作用如下：</p>
</blockquote>
<ul>
<li>Observer 接受客户端的连接，并将写请求转发给 Leader 节点，与 Leader 同步数据</li>
<li>不参与 Leader 选举，没有投票权。也不参与写操作的提议过程</li>
<li>数据没有事务化到硬盘。即 Observer 只会把数据加载到内存</li>
<li>加入更多 Observer 节点，提高伸缩性，同时不影响吞吐率</li>
</ul>
</li>
</ul>
</li>
</ul>
<h4 id="3-Zookeeper-的节点数怎么设置比较好？"><a href="#3-Zookeeper-的节点数怎么设置比较好？" class="headerlink" title="3.Zookeeper 的节点数怎么设置比较好？"></a>3.Zookeeper 的节点数怎么设置比较好？</h4><blockquote>
<p>2n - 1，由于 Leader 选举机制的超半数原则，2n - 1 台和 2n 台机器的容灾能力相同</p>
<p>安装奇数台</p>
<p>10 台服务器：3 台 zk<br>20 台服务器：5 台 zk<br>100 台服务器：11 台 zk<br>200 台服务器：11 台 zk</p>
<p><strong>服务器台数多：好处，提高可靠性；坏处：提高通信延时</strong></p>
</blockquote>
<h4 id="4-Zookeeper-架构"><a href="#4-Zookeeper-架构" class="headerlink" title="4.Zookeeper 架构"></a>4.Zookeeper 架构</h4><p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Zookeeper/Zookpeeper%E7%9A%84%E5%9F%BA%E6%9C%AC%E6%9E%B6%E6%9E%84.jpg" alt="Zookpeeper的基本架构"></p>
<ul>
<li>每个 Server 在内存中存储了一份数据</li>
<li>Zookeeper 启动时，将从实例中选举一个 leader（<code>Paxos</code> 协议）</li>
<li>Leader 负责处理数据更新等操作（<code>Zab</code> 协议）</li>
<li>一个更新操作成功，当且仅当大多数 Server 在内存中成功修改数据</li>
</ul>
<h4 id="5-Zookeeper-的功能有哪些"><a href="#5-Zookeeper-的功能有哪些" class="headerlink" title="5.Zookeeper 的功能有哪些"></a>5.Zookeeper 的功能有哪些</h4><ul>
<li><p>统一命名服务</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Zookeeper/%E7%BB%9F%E4%B8%80%E5%91%BD%E5%90%8D%E6%9C%8D%E5%8A%A1.png" alt="统一命名服务"></p>
</li>
<li><p>统一配置管理</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Zookeeper/%E7%BB%9F%E4%B8%80%E9%85%8D%E7%BD%AE%E7%AE%A1%E7%90%86.png" alt="统一配置管理"></p>
</li>
<li><p>统一集群管理</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Zookeeper/%E7%BB%9F%E4%B8%80%E9%9B%86%E7%BE%A4%E7%AE%A1%E7%90%86.png" alt="统一集群管理"></p>
</li>
<li><p>服务器节点动态上下线</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Zookeeper/%E6%9C%8D%E5%8A%A1%E5%99%A8%E5%8A%A8%E6%80%81%E4%B8%8A%E4%B8%8B%E7%BA%BF.png" alt="服务器动态上下线"></p>
</li>
<li><p>软负载均衡</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Zookeeper/%E8%BD%AF%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1.png" alt="软负载均衡"></p>
</li>
</ul>
<h4 id="6-Zookeeper-的数据结构（树）？"><a href="#6-Zookeeper-的数据结构（树）？" class="headerlink" title="6.Zookeeper 的数据结构（树）？"></a>6.Zookeeper 的数据结构（树）？</h4><blockquote>
<p>ZooKeeper 提供的名称空间与标准文件系统的名称空间非常相似，名称是由斜杠（&#x2F;）分隔的一系列路径元素。ZooKeeper 命名空间中的每个节点都由路径进行唯一标识</p>
<p>ZooKeeper 的层次命名空间与标准文件系统不同，ZooKeeper 命名空间中的每个节点都可以具有与其关联的数据以及子节点。就像拥有一个文件系统一样，该文件系统也允许文件成为目录。 每一个节点都可以存储数据，只是需要注意的是存储的容量是有限，一般不能超过 1MB</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Zookeeper/Zookeeper%E7%9A%84%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84.png" alt="Zookeeper的数据结构"></p>
<blockquote>
<p>Znode 的类型分为三类：</p>
</blockquote>
<ul>
<li>持久节点（persistent node）节点会被持久</li>
<li>临时节点（ephemeral node），客户端断开连接后，ZooKeeper 会自动删除临时节点</li>
<li>顺序节点（sequential node），每次创建顺序节点时，ZooKeeper 都会在路径后面自动添加上 10 位的数字，从 1 开始，最大是 2147483647 （2^32-1） 每个顺序节点都有一个单独的计数器，并且单调递增的，由 Zookeeper 的 leader 实例维护</li>
</ul>
<blockquote>
<p>Znode 实际上有四种形式，默认是<code>persistent</code></p>
</blockquote>
<ul>
<li><code>PERSISTENT</code>  持久节点： 如 <code>create /test/a  &quot;hello&quot;</code> ，通过 <code>create &lt;path&gt; &lt;data&gt;</code> 参数指定为持久节点</li>
<li><code>PERSISTENT_SEQUENTIAL</code>（持久顺序节点 <code>/s0000000001</code>） ，通过 <code>create -s &lt;path&gt; &lt;data&gt;</code>参数指定为顺序节点</li>
<li><code>EPHEMERAL</code>  临时节点，通过 <code>create -e &lt;path&gt; &lt;data&gt;</code> 参数指定为临时节点</li>
<li><code>EPHEMERAL_SEQUENTIAL</code>（临时顺序节点 <code>/s0000000001</code>） ，通过 <code>create -s -e &lt;path&gt; &lt;data&gt;</code> 参数指定为临时及顺序节点</li>
</ul>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Zookeeper/Zookeeper%E8%8A%82%E7%82%B9.png" alt="Zookeeper节点"></p>
<h4 id="7-Zookeeper-集群管理"><a href="#7-Zookeeper-集群管理" class="headerlink" title="7.Zookeeper 集群管理"></a>7.Zookeeper 集群管理</h4><blockquote>
<p>对集群管理分为两部分，一部分是监控集群中服务的运行状态，另一部分是操作集群中的服务的上下线</p>
<p>实现思路如下</p>
</blockquote>
<ul>
<li><p>监控集群中服务的运行状态</p>
<blockquote>
<p>服务消费者想要获取服务提供者集群的地址列表时，就可以从 Zookeeper 服务端获取服务提供者创建的临时节点，并且把这些信息缓存到本地，然后对这些临时节点的父节点开启监听</p>
<p>只要临时节点发生变化，服务消费者就会收到 Zookeeper 服务端的通知，并对本地缓存的地址信息进行更新</p>
</blockquote>
</li>
<li><p>操作集群中服务的上下线</p>
<blockquote>
<p>可以让服务在启动时就把本身的地址信息注册到 Zookeeper 的临时节点上，只要这个服务一直在线，这个临时节点就会一直存在，只要这个服务与 Zookeeper 服务端断开连接时，我们就认为这个服务不可用，Zookeeper 服务端就会将这个临时节点移除</p>
<p>还有一种情况是我们需要这个服务下线时，我们可以利用 Zookeeper 客户端提供的 API 来删除这个临时节点</p>
</blockquote>
</li>
</ul>
<h4 id="8-介绍下-Zookeeper-消息的发布订阅功能"><a href="#8-介绍下-Zookeeper-消息的发布订阅功能" class="headerlink" title="8.介绍下 Zookeeper 消息的发布订阅功能"></a>8.介绍下 Zookeeper 消息的发布订阅功能</h4><ul>
<li>数据发布 &#x2F; 订阅（Publish &#x2F; Subscribe）系统，即所谓的配置中心，顾名思义就是发布者将数据发布到 ZooKeeper 的一个或一系列节点上，供订阅者进行数据订阅，进而达到动态获取数据的目的，实现配置信息的集中式管理和数据的动态更新</li>
<li>发布 &#x2F; 订阅系统一般有两种设计模式，分别是推（Push）模式和拉（Pull）模式。在推模式中，服务端主动将数据更新发送给所有订阅的客户端；而拉模式则是由客户端主动发起请求来获取最新数据，通常客户端都采用定时进行轮询拉取的方式</li>
<li>ZooKeeper 采用的是<strong>推拉相结合</strong>的方式∶客户端向服务端注册自己需要关注的节点，一旦该节点的数据发生变更，那么服务端就会向相应的客户端发送 Watcher 事件通知，客户端接收到这个消息通知之后，需要主动到服务端获取最新的数据</li>
<li>如果将配置信息存放到 Zookeeper 上进行集中管理，那么通常情况下，应用在启动的时候都会主动到 Zookeeper 服务端上进行一次配置信息的获取，同时在指定节点上注册一个 Watcher 监听，但凡配置信息发生变更，服务端都会实时通知到所有订阅的客户端，从而达到实时获取最新配置信息的目的</li>
</ul>
<blockquote>
<p>适用于全局配置信息，全局配置信息有以下特点</p>
</blockquote>
<ul>
<li>数据量通常比较小</li>
<li>数据内容在运行时会发生动态变化</li>
<li>集群中各机器共享，配置一致</li>
</ul>
<h4 id="9-Zookeeper-的分布式锁实现方式？"><a href="#9-Zookeeper-的分布式锁实现方式？" class="headerlink" title="9.Zookeeper 的分布式锁实现方式？"></a>9.Zookeeper 的分布式锁实现方式？</h4><blockquote>
<p>前置知识，Zookeeper 的特点</p>
</blockquote>
<ul>
<li>维护了一个有层次的数据节点，类似文件系统</li>
<li>有以下数据节点：临时节点、持久节点、<strong>临时有序节点（分布式锁实现基于的数据节点）</strong>、持久有序节点</li>
<li>Zookeeper 可以和 client 客户端通过心跳的机制保持长连接，如果客户端链接 zookeeper 创建了一个临时节点，那么这个客户端与 zookeeper 断开连接后会自动删除</li>
<li>Zookeeper 的节点上可以注册上用户事件（自定义），如果节点数据删除等事件都可以触发自定义事件</li>
<li>Zookeeper 保持了统一视图，各服务对于状态信息获取满足一致性</li>
</ul>
<blockquote>
<p>Zookeeper 实现分布式锁的思路：</p>
<p>每个客户端对某个方法加锁时，在 Zookeeper 上与该方法对应的指定节点的目录下，生成一个唯一的临时有序节点。 判断是否获取锁的方式很简单，只需要判断有序节点中序号最小的一个。 当释放锁的时候只需将这个临时节点删除即可。同时其可以避免服务宕机导致的锁无法释放，而产生的死锁问题</p>
</blockquote>
<ul>
<li><p>Zookeeper 实现排他锁</p>
<blockquote>
<p>排他锁，又称写锁或独占锁。如果事务 T1 对数据对象 O1 加上了排他锁，那么在整个加锁期间，只允许事务 T1 对 O1 进行读取或更新操作，其他任务事务都不能对这个数据对象进行任何操作，直到 T1 释放了排他锁</p>
<p><strong>排他锁核心是保证当前有且仅有一个事务获得锁，并且锁释放之后，所有正在等待获取锁的事务都能够被通知到</strong></p>
<p>Zookeeper 的强一致性特性，能够很好地保证在分布式高并发情况下节点的创建一定能够保证全局唯一性，即 Zookeeper 将会保证客户端无法重复创建一个已经存在的数据节点。可以利用 Zookeeper 这个特性，实现排他锁</p>
</blockquote>
<ul>
<li><p>定义锁：通过 Zookeeper 上的数据节点来表示一个锁</p>
</li>
<li><p>获取锁：客户端通过调用 create 方法创建表示锁的临时节点，可以认为创建成功的客户端获得了锁，同时可以让没有获得锁的节点在该节点上注册 Watcher 监听，以便实时监听到 lock 节点的变更情况</p>
</li>
<li><p>释放锁：以下两种情况都可以让锁释放</p>
<ul>
<li>当前获得锁的客户端发生宕机或异常，那么 Zookeeper 上这个临时节点就会被删除</li>
<li>正常执行完业务逻辑，客户端主动删除自己创建的临时节点</li>
</ul>
<img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Zookeeper/%E6%8E%92%E4%BB%96%E9%94%81.png" alt="排他锁" style="zoom:50%;" /></li>
</ul>
</li>
<li><p>Zookeeper 实现共享锁</p>
<blockquote>
<p>共享锁，又称读锁。如果事务 T1 对数据对象 O1 加上了共享锁，那么当前事务只能对 O1 进行读取操作，其他事务也只能对这个数据对象加共享锁，直到该数据对象上的所有共享锁都被释放</p>
<p>共享锁与排他锁的区别在于，加了排他锁之后，数据对象只对当前事务可见，而加了共享锁之后，数据对象对所有事务都可见</p>
</blockquote>
<ul>
<li><p>定义锁：通过 Zookeeper 上的数据节点来表示一个锁，是一个类似于 <code>/lockpath/[hostname]-请求类型-序号</code> 的临时顺序节点</p>
</li>
<li><p>获取锁：客户端通过调用 create 方法创建表示锁的临时顺序节点，如果是读请求则创建 <code>/lockpath/[hostname]-R-序号节点</code>，如果是写请求则创建 <code>/lockpath/[hostname]-W-序号节点</code></p>
</li>
<li><p>判断读写顺序：大概分为 4 个步骤</p>
<ul>
<li><p>创建完节点后，获取 <code>/lockpath</code> 节点下的所有子节点，并对该节点注册子节点变更的 Watcher 监听</p>
</li>
<li><p>确定自己的节点序号在所有子节点中的顺序</p>
<blockquote>
<p>对于读请求：如果没有比自己序号更小的子节点，或者比自己序号小的子节点都是读请求，那么表明自己已经成功获取到了共享锁，同时开始执行读取逻辑；如果有比自己序号小的子节点有写请求，那么等待</p>
<p>对于写请求，如果自己不是序号最小的节点，那么等待</p>
</blockquote>
</li>
<li><p>接收到 Watcher 通知后，重复步骤这三个步骤</p>
</li>
</ul>
</li>
<li><p>释放锁：与排他锁逻辑一致</p>
<img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Zookeeper/%E5%85%B1%E4%BA%AB%E9%94%81.png" alt="共享锁" style="zoom:50%;" /></li>
</ul>
</li>
<li><p>Zookeeper 实现改进共享锁</p>
<blockquote>
<p>在实现共享锁的 “判断读写顺序” 时：创建完节点后，获取 <code>/lockpath</code> 节点下的所有子节点，并对该节点注册子节点变更的 Watcher 监听</p>
<p>这样的话，任何一次客户端移除共享锁之后，Zookeeper 将会发送子节点变更的 Watcher 给所有机器，系统中将有大量的 <code>Watcher 通知</code> 和 <code>子节点列表获取</code> 操作重复执行，然后所有节点再判断自己是否是序号最小的节点（写请求）或者判断比自己序号小的子节点是否都是读请求（读请求），从而继续等待下一次通知</p>
<p>然而这些重复操作很多都是<code>无用的</code>，实际上每个锁竞争者只需要关注序号比自己小的那个节点是否存在即可</p>
<p>当集群规模比较大时，这些<code>无用的</code>操作不仅会对 Zookeeper 造成巨大的性能影响和网络冲击，更为严重的是，如果同一时间有多个客户端释放了共享锁，Zookeeper 服务器就会在短时间内向其余客户端发送大量的事件通知–这就是所谓的<code>羊群效应</code></p>
<p>改进后的分布式锁实现：</p>
</blockquote>
<ul>
<li><p>客户端调用 create 方法创建一个类似于 <code>/lockpath/[hostname]-请求类型-序号</code> 的临时顺序节点</p>
</li>
<li><p>客户端调用 getChildren 方法获取所有已经创建的子节点列表（这里不注册任何 Watcher）</p>
</li>
<li><p>如果无法获取任何共享锁，那么调用 exist 来对比自己小的那个节点注册 Watcher</p>
<blockquote>
<p>读请求：向比自己序号小的最后一个写请求节点注册 Watcher 监听</p>
<p>写请求：向比自己序号小的最后一个节点注册 Watcher 监听</p>
</blockquote>
</li>
<li><p>等待 Watcher 监听，继续进入第二步</p>
</li>
</ul>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Zookeeper/%E6%94%B9%E8%BF%9B%E5%85%B1%E4%BA%AB%E9%94%81.png" alt="改进共享锁"></p>
</li>
</ul>
<h4 id="10-Zookeeper-怎么保证一致性的"><a href="#10-Zookeeper-怎么保证一致性的" class="headerlink" title="10.Zookeeper 怎么保证一致性的"></a>10.Zookeeper 怎么保证一致性的</h4><blockquote>
<p>在分布式场景中，ZooKeeper 的应用非常广泛，比如数据发布和订阅、命名服务、配置中心、注册中心、分布式锁等</p>
<p>ZooKeeper 提供了一个类似 Linux 文件系统的数据模型，和基于 Watcher 机制的分布式事件通知，这些特性都依赖 ZooKeeper 的高容错数据一致性协议</p>
<p>那么问题来了，在分布式场景下，ZooKeeper 是如何实现数据一致性的呢？</p>
<p><strong>ZooKeeper 是通过 Zab 协议来保证分布式事务的最终一致性</strong></p>
</blockquote>
<ul>
<li><p>Zookeeper 的 <strong>zab 协议</strong>（原子广播协议）</p>
<blockquote>
<p>Zab（ZooKeeper Atomic Broadcast，ZooKeeper 原子广播协议）支持崩溃恢复，基于该协议，ZooKeeper 实现了一种主备模式的系统架构来保持集群中各个副本之间数据一致性 </p>
<p>在 ZooKeeper 集群中，所有客户端的请求都是写入到 Leader 进程中的，然后，由 Leader 同步到其他节点，称为 Follower</p>
<p>在集群数据同步的过程中，如果出现 Follower 节点崩溃或者 Leader 进程崩溃时，都会通过 Zab 协议来保证数据一致性</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Zookeeper/%E4%B8%BB%E5%86%99%E4%BB%8E%E5%90%8C%E6%AD%A5.png" alt="主写从同步"></p>
<blockquote>
<p>Zab 协议的具体实现可以分为以下两部分</p>
<p>整个 ZooKeeper 集群的一致性保证就是在这两个状态之前切换，当 Leader 服务正常时，就是正常的消息广播模式；当 Leader 不可用时，则进入崩溃恢复模式，崩溃恢复阶段会进行数据同步，完成以后，重新进入消息广播阶段</p>
</blockquote>
<ul>
<li><p><strong>消息广播阶段</strong></p>
<p>Leader 节点接受事务提交，并且将新的 Proposal 请求广播给 Follower 节点，收集各个节点的反馈，决定是否进行 Commit，在这个过程中，也会使用 [Quorum 选举机制](#Quorum 选举机制)</p>
</li>
<li><p><strong>崩溃恢复阶段</strong></p>
<p>如果在同步过程中出现 Leader 节点宕机，会进入崩溃恢复阶段，重新进行 Leader 选举，崩溃恢复阶段还包含数据同步操作，同步集群中最新的数据，保持集群的数据一致性</p>
</li>
</ul>
<blockquote>
<p>在学习 Zab 具体流程之前，先来看下 Zxid 的概念</p>
<p>Zxid 是 Zab 协议的一个事务编号，Zxid 是一个 64 位的数字，其中低 32 位是一个简单的单调递增计数器，针对客户端每一个事务请求，计数器加 1；而高 32 位则代表 Leader 周期年代的编号</p>
<p>这里 Leader 周期的英文是 epoch，可以理解为当前集群所处的年代或者周期</p>
<p>每当有一个新的 Leader 选举出现时，就会从这个 Leader 服务器上取出其本地日志中最大事务的 Zxid，并从中读取 epoch 值，然后加 1，以此作为新的周期 ID</p>
<p>总结一下，高 32 位代表了每代 Leader 的唯一性，低 32 位则代表了每代 Leader 中事务的唯一性</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Zookeeper/ZXid.png" alt="ZXid"></p>
</li>
<li><p>ZAB 具体流程</p>
<blockquote>
<p>Zab 的具体流程可以拆分为<strong>消息广播</strong>、<strong>崩溃恢复</strong>和<strong>数据同步</strong>三个过程</p>
</blockquote>
<ul>
<li><p><strong>消息广播</strong></p>
<blockquote>
<p>在 ZooKeeper 中所有的事务请求都由 Leader 节点来处理，其他服务器为 Follower，Leader 将客户端的事务请求转换为事务 Proposal，并且将 Proposal 分发给集群中其他所有的 Follower</p>
<p>完成广播之后，Leader 等待 Follwer 反馈，当有过半数的 Follower 反馈信息后，Leader 将再次向集群内 Follower 广播 Commit 信息，Commit 信息就是确认将之前的 Proposal 提交</p>
<p>这里的 Commit 可以对比 SQL 中的 COMMIT 操作来理解，MySQL 默认操作模式是 autocommit 自动提交模式，如果你显式地开始一个事务，在每次变更之后都要通过 COMMIT 语句来确认，将更改提交到数据库中</p>
<p>Leader 节点的写入也是一个两步操作，第一步是<strong>广播事务操作</strong>，第二步是<strong>广播提交操作</strong>，其中过半数指的是反馈的节点数 &gt;&#x3D;N&#x2F;2+1，N 是全部的 Follower 节点数量</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Zookeeper/zab%E6%B6%88%E6%81%AF%E5%B9%BF%E6%92%AD.png" alt="zab消息广播"></p>
<ul>
<li>客户端的写请求进来之后，Leader 会将写请求包装成 Proposal 事务，并添加一个递增事务 ID，也就是 Zxid，Zxid 是单调递增的，以保证每个消息的先后顺序</li>
<li>广播这个 Proposal 事务，Leader 节点和 Follower 节点是解耦的，通信都会经过一个先进先出的消息队列，Leader 会为每一个 Follower 服务器分配一个单独的 FIFO 队列，然后把 Proposal 放到队列中</li>
<li>Follower 节点收到对应的 Proposal 之后会把它持久到磁盘上，当完全写入之后，发一个 ACK 给 Leader</li>
<li>当 Leader 收到超过半数 Follower 机器的 ack 之后，会提交本地机器上的事务，同时开始广播 commit， Follower 收到 commit 之后，完成各自的事务提交</li>
</ul>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Zookeeper/zab1.png" alt="zab1"></p>
</li>
<li><h5 id="崩溃恢复"><a href="#崩溃恢复" class="headerlink" title="崩溃恢复"></a>崩溃恢复</h5><blockquote>
<p>消息广播通过 Quorum 机制，解决了 Follower 节点宕机的情况，但是如果在广播过程中 Leader 节点崩溃呢？</p>
<p>这就需要 Zab 协议支持的崩溃恢复，崩溃恢复可以保证在 Leader 进程崩溃的时候可以重新选出 Leader，并且保证数据的完整性</p>
<p>崩溃恢复和集群启动时的选举过程是一致的，也就是说，下面的几种情况都会进入崩溃恢复阶段：</p>
</blockquote>
<ul>
<li>初始化集群，刚刚启动的时候</li>
<li>Leader 崩溃，因为故障宕机</li>
<li>Leader 失去了半数的机器支持，与集群中超过一半的节点断连</li>
</ul>
<blockquote>
<p>崩溃恢复模式将会开启新的一轮选举，选举产生的 Leader 会与过半的 Follower 进行同步，使数据一致，当与过半的机器同步完成后，就退出恢复模式， 然后进入消息广播模式</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Zookeeper/zab2.png" alt="zab2"></p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Zookeeper/zab3.png" alt="zab3"></p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Zookeeper/zab4.png" alt="zab4"></p>
</li>
<li><p><strong>数据同步</strong></p>
<blockquote>
<p>崩溃恢复完成选举以后，接下来的工作就是数据同步，在选举过程中，通过投票已经确认 Leader 服务器是最大 Zxid 的节点，同步阶段就是利用 Leader 前一阶段获得的最新 Proposal 历史，同步集群中所有的副本</p>
</blockquote>
</li>
</ul>
</li>
<li><p>ZAB 是以 Paxos 算法为基础的</p>
<blockquote>
<p>Paxos 的思想在很多分布式组件中都可以看到，Zab 协议可以认为是基于 Paxos 算法实现的，先来看下两者之间的联系</p>
</blockquote>
<ul>
<li>都存在一个 Leader 进程的角色，负责协调多个 Follower 进程的运行</li>
<li>都应用 Quorum 机制，Leader 进程都会等待超过半数的 Follower 做出正确的反馈后，才会将一个提案进行提交</li>
<li>在 Zab 协议中，Zxid 中通过 epoch 来代表当前 Leader 周期，在 Paxos 算法中，同样存在这样一个标识，叫做 Ballot Number</li>
</ul>
<blockquote>
<p>两者之间的区别是，Paxos 是理论，Zab 是实践，Paxos 是论文性质的，目的是设计一种通用的分布式一致性算法，而 Zab 协议应用在 ZooKeeper 中，是一个特别设计的崩溃可恢复的原子消息广播算法</p>
<p>Zab 协议增加了崩溃恢复的功能，当 Leader 服务器不可用，或者已经半数以上节点失去联系时，ZooKeeper 会进入恢复模式选举新的 Leader 服务器，使集群达到一个一致的状态</p>
</blockquote>
</li>
</ul>
<h4 id="11-Zookeeper-的通知机制"><a href="#11-Zookeeper-的通知机制" class="headerlink" title="11.Zookeeper 的通知机制"></a>11.Zookeeper 的通知机制</h4><blockquote>
<p><a target="_blank" rel="noopener" href="https://blog.csdn.net/weixin_41605937/article/details/122095904">参考文章，含代码实现</a></p>
<p>客户端注册监听他关心的目录节点，当目录节点发生变化（数据改变、被删除、子目录节点增加删除）时，zookeeper 会通知客户端</p>
<p>client 端会对某个 znode 建立一个 watcher 事件，当该 znode 发生变化时，zk 会主动通知 watch 这个 znode 的 client，然后 client 根据 znode 的变化来做出业务上的改变等</p>
</blockquote>
<ul>
<li><p>znode</p>
<blockquote>
<p>Zookeeper 维护一个类似文件系统的数据结构</p>
<p>有点类似 windows 中注册表的结构，有名称，有树节点，有 kv 对的关系，可以看做一个树形结构的数据库，分布在不同的机器上做名称管理</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Zookeeper/znode.png" alt="znode"></p>
<blockquote>
<p>Znode 维护了一个 stat 结构，这个 stat 包含数据变化的版本号、访问控制列表变化、还有时间戳</p>
<p>版本号和时间戳一起，可让 Zookeeper 验证缓存和协调更新。每次 znode 的数据发生了变化，版本号就增加</p>
<p>无论何时客户端检索数据，它也一起检索数据的版本号。并且当客户端执行更新或删除时，客户端必须提供他正在改变的 znode 的版本号。如果它提供的版本号和真实的数据版本号不一致，更新将会失败</p>
</blockquote>
</li>
<li><p>watcher 的特点</p>
<ul>
<li>轻量级：一个 callback 函数</li>
<li>异步性：不会 block 正常的读写请求</li>
<li>主动推送：Watch 被触发时，由 Zookeeper 服务端主动将更新推送给客户端</li>
<li>一次性：数据变化时，Watch 只会被触发一次。如果客户端想得到后续更新的通知，必须要在 Watch 被触发后重新注册一个 Watch</li>
<li>仅通知：仅通知变更类型，不附带变更后的结果</li>
<li>顺序性：如果多个更新触发了多个 Watch，那 Watch 被触发的顺序与更新顺序一致</li>
</ul>
</li>
<li><p>使用 watch 的注意事项</p>
<ul>
<li>由于 watcher 是一次性的，所以需要自己去实现永久 watch</li>
<li>如果被 watch 的节点频繁更新，会出现<code>丢数据</code>的情况</li>
<li>watcher 数量过多会导致性能下降</li>
</ul>
</li>
</ul>
<h4 id="12-Zookeeper-脑裂问题"><a href="#12-Zookeeper-脑裂问题" class="headerlink" title="12.Zookeeper 脑裂问题"></a>12.Zookeeper 脑裂问题</h4><ul>
<li><p>脑裂问题定义</p>
<blockquote>
<p>简单点来说，脑裂（Split-Brain）就是比如当你的 cluster 里面有两个节点，它们都知道在这个 cluster 里需要选举出一个 master</p>
<p>那么当它们两个之间的通信完全没有问题的时候，就会达成共识，选出其中一个作为 master</p>
<p>但是如果它们之间的通信出了问题，那么两个结点都会觉得现在没有 master，所以每个都把自己选举成 master，于是 cluster 里面就会有两个 master</p>
<p>脑裂通常会出现在集群环境中，比如 ElasticSearch、Zookeeper 集群，而这些集群环境有一个统一的特点，就是它们有一个大脑，比如 ElasticSearch 集群中有 Master 节点，Zookeeper 集群中有 Leader 节点</p>
</blockquote>
</li>
<li><p>造成脑裂的原因</p>
<blockquote>
<p>主要原因是 Zookeeper 集群和 Zookeeper client 判断超时并不能做到完全同步，也就是说可能一前一后，如果是集群先于 client 发现，那就会出现脑裂的情况</p>
<p>同时在发现并切换后通知各个客户端也有先后快慢。一般出现这种情况的几率很小，需要 leader 节点与 Zookeeper 集群网络断开，但是与其他集群角色之间的网络没有问题，还要满足上面那些情况，但是一旦出现就会引起很严重的后果，数据不一致</p>
</blockquote>
</li>
<li><p>Zookeeper 解决脑裂的方法</p>
<blockquote>
<p>过半选举机制</p>
<p>举例如下</p>
</blockquote>
<blockquote>
<p>对于一个集群，想要提高这个集群的可用性，通常会采用多机房部署，比如现在有一个由 6 台 zkServer 所组成的一个集群（一般是奇数台，此处为了举例说明脑裂现象），部署在了两个机房：</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Zookeeper/%E8%84%91%E8%A3%821.jpg" alt="脑裂1"></p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Zookeeper/%E8%84%91%E8%A3%822.jpg" alt="脑裂2"></p>
<blockquote>
<p>正常情况下，此集群只会有一个 Leader，那么如果机房之间的网络断了之后，两个机房内的 zkServer 还是可以相互通信的，如果<strong>不考虑过半机制</strong>，那么就会出现每个机房内部都将选出一个 Leader</p>
<p>这就相当于原本一个集群，被分成了两个集群，出现了两个“大脑”，这就是脑裂</p>
<p>原本应该是统一的一个集群对外提供服务的，现在变成了两个集群同时对外提供服务，如果过了一会，断了的网络突然联通了，那么此时就会出现问题了，两个集群刚刚都对外提供服务了，数据该怎么合并，数据冲突怎么解决等等问题</p>
<p>而有了过半机制则可以避免脑裂现象，比如上述例子要求过半（大于，而非大于等于）即至少 4 台节点才能选出一个 Leader</p>
</blockquote>
</li>
</ul>
<h4 id="13-Paxos-算法"><a href="#13-Paxos-算法" class="headerlink" title="13.Paxos 算法"></a>13.Paxos 算法</h4><ul>
<li><p>Paxos 算法作用</p>
<p> <img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Zookeeper/paxos1.png" alt="paxos1"></p>
</li>
<li><p>Paxos 算法描述</p>
<p> <img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Zookeeper/paxos2.png" alt="paxos2"></p>
</li>
<li><p>Paxos 算法流程</p>
<p> <img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Zookeeper/paxos3.png" alt="paxos3"></p>
</li>
<li><p>Paxos 算法举例</p>
<blockquote>
<p>举例 1</p>
</blockquote>
<p> <img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Zookeeper/paxos%E4%B8%BE%E4%BE%8B1.png" alt="paxos举例1"></p>
<blockquote>
<p>举例 2</p>
</blockquote>
<p> <img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Zookeeper/paxos%E4%B8%BE%E4%BE%8B2.png" alt="paxos举例2"></p>
<blockquote>
<p>举例 3</p>
</blockquote>
<p> <img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Zookeeper/paxos%E4%B8%BE%E4%BE%8B3.png" alt="paxos举例3"></p>
<blockquote>
<p>造成这种情况的原因是系统中有一个以上的 Proposer，多个 Proposers 相互争夺 Acceptor，造成退退无法达成一致的情况</p>
<p>针对这种情况，一种改进的 Paxos 算法被提出：从系统中选出一个节点作为 Leader，只有 Leader 能够发起提案</p>
<p>这样，一次 Paxos 流程中只有一个Proposer，不会出现活锁的情况，此时只会出现举例 1 中情况</p>
</blockquote>
</li>
</ul>
<h4 id="14-Quorum-选举机制"><a href="#14-Quorum-选举机制" class="headerlink" title="14.Quorum 选举机制"></a>14.Quorum 选举机制</h4><p><span id="Quorum 选举机制"></span></p>
<ul>
<li><p>前置知识</p>
<blockquote>
<p>在了解 Quorum 机制之前，先回顾一下数据一致性</p>
<p>显然能达到强一致性的效果是最好的，那么有哪些方式可以实现呢，其中最为简单直接的就是 WARO，也就是 <code>Write All Read one</code></p>
</blockquote>
<ul>
<li>强一致性：在任意时刻，从任意不同副本取出的值都是一样的</li>
<li>弱一致性：有时泛指最终一致性，是指在任意时刻，可能由于网络延迟或者设备异常等原因，不同副本中的值可能会不一样，但经过一段时间后，最终会变成一样</li>
</ul>
</li>
<li><p>WARO 协议</p>
<blockquote>
<p>是一种简单的副本控制协议，当 Client 请求向某副本写数据时（更新数据），只有当所有的副本都更新成功之后，这次写操作才算成功，否则视为失败</p>
<p>这样的话，只需要读任何一个副本上的数据即可。但是 WARO 带来的影响是写服务的可用性较低，因为只要有一个副本更新失败，此次写操作就视为失败了</p>
</blockquote>
</li>
<li><p>Quorum 机制</p>
<blockquote>
<p>Quorum 的定义如下：假设有 N 个副本，更新操作 wi 在 W 个副本中更新成功之后，则认为此次更新操作 wi 成功，把这次成功提交的更新操作对应的数据叫做：<code>成功提交的数据</code>。对于读操作而言，至少需要读 R 个副本，其中，W+R &gt; N ，即 W 和 R 有重叠，一般，W+R&#x3D;N+1</p>
</blockquote>
<ul>
<li>N &#x3D; 存储数据副本的数量</li>
<li>W &#x3D; 更新成功所需的副本</li>
<li>R &#x3D; 一次数据对象读取要访问的副本的数量</li>
</ul>
<blockquote>
<p>举个例子：</p>
<p>假设有 5 个副本，更新操作成功写入了 3 个，另外 2 个副本仍是旧数据，此时在读取的时候，只要确保读取副本的数量大于 2，那么肯定就会读到最新的数据。至于如何确定哪份数据是最新的，可以通过引入数据版本号的方式判断（Quorum 机制的使用需要配合一个获取最新成功提交的版本号的 metadata 服务，这样可以确定最新已经成功提交的版本号，然后从已经读到的数据中就可以确认最新写入的数据）</p>
</blockquote>
<blockquote>
<p>Quorum 机制的应用</p>
</blockquote>
<ul>
<li>Zookeeper 的选举机制是遵循了 Quorum 的，这也是为什部署 Zookeeper 必须要求有奇数个 Cluster 可用的原因<ul>
<li>一是能保证 Leader 选举时不会出现平票的情况，避免出现脑裂</li>
<li>二是 Leader 在向 Follower 同步数据的时候，必须要超过半数的 Follower 同步成功，才会认为数据写入成功</li>
</ul>
</li>
<li>其实除了 Zookeeper 以外，很多支持分布式部署的模块，也都遵循和使用了这个设计，比如 Redis 的哨兵（sentinel）机制</li>
<li>HDFS HA 中使用 Quorum 机制保证 Active 和 Standby 这两个 NameNode 中的数据一致</li>
</ul>
</li>
</ul>
<h4 id="15-Zookeeper-的数据存储在什么地方"><a href="#15-Zookeeper-的数据存储在什么地方" class="headerlink" title="15.Zookeeper 的数据存储在什么地方"></a>15.Zookeeper 的数据存储在什么地方</h4><blockquote>
<p>在 ZooKeeper 中，数据存储分为两部分： 内存数据存储与磁盘数据存储</p>
</blockquote>
<ul>
<li><p>内存数据存储</p>
<blockquote>
<p>Zookeeper 的数据模型是树结构，在内存数据库中，存储了整棵树的内容，包括所有的节点路径、节点数据、ACL信息，Zookeeper 会定时将这个数据存储到磁盘上</p>
</blockquote>
<ul>
<li><p>DataTree</p>
<blockquote>
<p>DataTree 是内存数据存储的核心，是一个树结构，代表了内存中一份完整的数据</p>
<p>DataTree 不包含任何与网络、客户端连接及请求处理相关的业务逻辑，是一个独立的组件</p>
</blockquote>
</li>
<li><p>DataNode</p>
<blockquote>
<p>DataNode 是数据存储的最小单元，其内部除了保存了结点的数据内容、ACL列表、节点状态之外，还记录了父节点的引用和子节点列表两个属性，其也提供了对子节点列表进行操作的接口</p>
</blockquote>
</li>
<li><p>ZKDatabase</p>
<blockquote>
<p>Zookeeper 的内存数据库，管理 Zookeeper 的所有会话、DataTree 存储和事务日志</p>
<p>ZKDatabase 会定时向磁盘 dump 快照数据，同时在 Zookeeper 启动时，会通过磁盘的事务日志和快照文件恢复成一个完整的内存数据库</p>
</blockquote>
</li>
</ul>
</li>
<li><p>磁盘数据存储</p>
<blockquote>
<p>数据快照是 Zookeeper 数据存储中非常核心的运行机制，数据快照用来记录 Zookeeper 服务器上某一时刻的全量内存数据内容，并将其写入指定的磁盘文件中</p>
</blockquote>
<ul>
<li><p>文件存储</p>
<blockquote>
<p>与事务文件类似，Zookeeper 快照文件也可以指定特定磁盘目录，通过 dataDir 属性来配置</p>
<p>若指定 dataDir 为 <code>/home/admin/zkData/zk_data</code>，则在运行过程中会在该目录下创建 version-2 的目录，该目录确定了当前 Zookeeper 使用的快照数据格式版本号</p>
<p>在 Zookeeper 运行时，会生成一系列文件</p>
</blockquote>
</li>
<li><p>数据快照</p>
<blockquote>
<p>FileSnap 负责维护快照数据对外的接口，包括快照数据的写入和读取等，将内存数据库写入快照数据文件其实是一个序列化过程</p>
<p>针对客户端的每一次事务操作，Zookeeper 都会将他们记录到事务日志中，同时也会将数据变更应用到内存数据库中，Zookeeper 在进行若干次事务日志记录后，将内存数据库的全量数据 Dump 到本地文件中，这就是数据快照</p>
<p>其步骤如下</p>
</blockquote>
<ul>
<li>确定是否需要进行数据快照：每进行一次事务日志记录之后，Zookeeper 都会检测当前是否需要进行数据快照，考虑到数据快照对于 Zookeeper 机器的影响，需要尽量避免 Zookeeper 集群中的所有机器在同一时刻进行数据快照。采用过半随机策略进行数据快照操作</li>
<li>切换事务日志文件：表示当前的事务日志已经写满，需要重新创建一个新的事务日志</li>
<li>创建数据快照异步线程：创建单独的异步线程来进行数据快照以避免影响 Zookeeper 主流程</li>
<li>获取全量数据和会话信息：从 ZKDatabase 中获取到 DataTree 和会话信息</li>
<li>生成快照数据文件名：Zookeeper 根据当前已经提交的最大 ZXID 来生成数据快照文件名</li>
<li>数据序列化：首先序列化文件头信息，然后再对会话信息和 DataTree 分别进行序列化，同时生成一个 Checksum，一并写入快照数据文件中去</li>
</ul>
</li>
</ul>
</li>
</ul>
<h4 id="16-Zookeeper-从三台扩容到七台怎么做"><a href="#16-Zookeeper-从三台扩容到七台怎么做" class="headerlink" title="16.Zookeeper 从三台扩容到七台怎么做"></a>16.Zookeeper 从三台扩容到七台怎么做</h4><blockquote>
<p><a target="_blank" rel="noopener" href="https://www.cnblogs.com/rexcheny/articles/9381374.html">参考文章</a></p>
</blockquote>
</article><div class="post-copyright"><div class="post-copyright__author"><span class="post-copyright-meta">文章作者: </span><span class="post-copyright-info"><a href="https://gitee.com/zcmmmm/zcmmmm">zcm</a></span></div><div class="post-copyright__type"><span class="post-copyright-meta">文章链接: </span><span class="post-copyright-info"><a href="https://gitee.com/zcmmmm/zcmmmm/2022/12/10/zookeeper%E9%9D%A2%E7%BB%8F/">https://gitee.com/zcmmmm/zcmmmm/2022/12/10/zookeeper%E9%9D%A2%E7%BB%8F/</a></span></div><div class="post-copyright__notice"><span class="post-copyright-meta">版权声明: </span><span class="post-copyright-info">本博客所有文章除特别声明外，均采用 <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" target="_blank">CC BY-NC-SA 4.0</a> 许可协议。转载请注明来自 <a href="https://gitee.com/zcmmmm/zcmmmm" target="_blank">无人深空</a>！</span></div></div><div class="tag_share"><div class="post-meta__tag-list"><a class="post-meta__tags" href="/tags/Zookeeper/">Zookeeper</a></div><div class="post_share"><div class="social-share" data-image="https://study-record-images.oss-cn-beijing.aliyuncs.com/Zookeeper/Zookeeper.png" data-sites="facebook,twitter,wechat,weibo,qq"></div><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/butterfly-extsrc/sharejs/dist/css/share.min.css" media="print" onload="this.media='all'"><script src="https://cdn.jsdelivr.net/npm/butterfly-extsrc/sharejs/dist/js/social-share.min.js" defer></script></div></div><nav class="pagination-post" id="pagination"><div class="prev-post pull-left"><a href="/2023/03/03/Java%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B/"><img class="prev-cover" src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/FunctionalProgramming/Functional%20programmi.png" onerror="onerror=null;src='/img/404.jpg'" alt="cover of previous post"><div class="pagination-info"><div class="label">上一篇</div><div class="prev_info">Java函数式编程</div></div></a></div><div class="next-post pull-right"><a href="/2022/12/05/Hadoop%E9%9D%A2%E7%BB%8F/"><img class="next-cover" src="https://study-record-images.oss-cn-beijing.aliyuncs.com/BigData/Hadoop/Hadoop.png" onerror="onerror=null;src='/img/404.jpg'" alt="cover of next post"><div class="pagination-info"><div class="label">下一篇</div><div class="next_info">Hadoop 面经</div></div></a></div></nav><div class="relatedPosts"><div class="headline"><i class="fas fa-thumbs-up fa-fw"></i><span>相关推荐</span></div><div class="relatedPosts-list"><div><a href="/2022/11/05/zookeeper%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86/" title="Zookeeper基础知识"><img class="cover" src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Zookeeper/Zookeeper.png" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2022-11-05</div><div class="title">Zookeeper基础知识</div></div></a></div></div></div></div><div class="aside-content" id="aside-content"><div class="sticky_layout"><div class="card-widget" id="card-toc"><div class="item-headline"><i class="fas fa-stream"></i><span>目录</span><span class="toc-percentage"></span></div><div class="toc-content is-expand"><ol class="toc"><li class="toc-item toc-level-2"><a class="toc-link" href="#Zookeeper-%E9%9D%A2%E7%BB%8F"><span class="toc-text">Zookeeper 面经</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#1-%E4%BB%8B%E7%BB%8D%E4%B8%8B-Zookeeper"><span class="toc-text">1.介绍下 Zookeeper</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#2-Zookeeper-%E7%9A%84%E9%80%89%E4%B8%BE%E7%AD%96%E7%95%A5"><span class="toc-text">2.Zookeeper 的选举策略</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#3-Zookeeper-%E7%9A%84%E8%8A%82%E7%82%B9%E6%95%B0%E6%80%8E%E4%B9%88%E8%AE%BE%E7%BD%AE%E6%AF%94%E8%BE%83%E5%A5%BD%EF%BC%9F"><span class="toc-text">3.Zookeeper 的节点数怎么设置比较好？</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#4-Zookeeper-%E6%9E%B6%E6%9E%84"><span class="toc-text">4.Zookeeper 架构</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#5-Zookeeper-%E7%9A%84%E5%8A%9F%E8%83%BD%E6%9C%89%E5%93%AA%E4%BA%9B"><span class="toc-text">5.Zookeeper 的功能有哪些</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#6-Zookeeper-%E7%9A%84%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%EF%BC%88%E6%A0%91%EF%BC%89%EF%BC%9F"><span class="toc-text">6.Zookeeper 的数据结构（树）？</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#7-Zookeeper-%E9%9B%86%E7%BE%A4%E7%AE%A1%E7%90%86"><span class="toc-text">7.Zookeeper 集群管理</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#8-%E4%BB%8B%E7%BB%8D%E4%B8%8B-Zookeeper-%E6%B6%88%E6%81%AF%E7%9A%84%E5%8F%91%E5%B8%83%E8%AE%A2%E9%98%85%E5%8A%9F%E8%83%BD"><span class="toc-text">8.介绍下 Zookeeper 消息的发布订阅功能</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#9-Zookeeper-%E7%9A%84%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E5%AE%9E%E7%8E%B0%E6%96%B9%E5%BC%8F%EF%BC%9F"><span class="toc-text">9.Zookeeper 的分布式锁实现方式？</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#10-Zookeeper-%E6%80%8E%E4%B9%88%E4%BF%9D%E8%AF%81%E4%B8%80%E8%87%B4%E6%80%A7%E7%9A%84"><span class="toc-text">10.Zookeeper 怎么保证一致性的</span></a><ol class="toc-child"><li class="toc-item toc-level-5"><a class="toc-link" href="#%E5%B4%A9%E6%BA%83%E6%81%A2%E5%A4%8D"><span class="toc-text">崩溃恢复</span></a></li></ol></li><li class="toc-item toc-level-4"><a class="toc-link" href="#11-Zookeeper-%E7%9A%84%E9%80%9A%E7%9F%A5%E6%9C%BA%E5%88%B6"><span class="toc-text">11.Zookeeper 的通知机制</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#12-Zookeeper-%E8%84%91%E8%A3%82%E9%97%AE%E9%A2%98"><span class="toc-text">12.Zookeeper 脑裂问题</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#13-Paxos-%E7%AE%97%E6%B3%95"><span class="toc-text">13.Paxos 算法</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#14-Quorum-%E9%80%89%E4%B8%BE%E6%9C%BA%E5%88%B6"><span class="toc-text">14.Quorum 选举机制</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#15-Zookeeper-%E7%9A%84%E6%95%B0%E6%8D%AE%E5%AD%98%E5%82%A8%E5%9C%A8%E4%BB%80%E4%B9%88%E5%9C%B0%E6%96%B9"><span class="toc-text">15.Zookeeper 的数据存储在什么地方</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#16-Zookeeper-%E4%BB%8E%E4%B8%89%E5%8F%B0%E6%89%A9%E5%AE%B9%E5%88%B0%E4%B8%83%E5%8F%B0%E6%80%8E%E4%B9%88%E5%81%9A"><span class="toc-text">16.Zookeeper 从三台扩容到七台怎么做</span></a></li></ol></li></ol></li></ol></div></div></div></div></main><footer id="footer"><div id="footer-wrap"><div class="copyright">&copy;2022 - 2023 By zcm</div><div class="framework-info"><span>框架 </span><a target="_blank" rel="noopener" href="https://hexo.io">Hexo</a><span class="footer-separator">|</span><span>主题 </span><a target="_blank" rel="noopener" href="https://github.com/jerryc127/hexo-theme-butterfly">Butterfly</a></div></div></footer></div><div id="rightside"><div id="rightside-config-hide"><button id="readmode" type="button" title="阅读模式"><i class="fas fa-book-open"></i></button><button id="darkmode" type="button" title="浅色和深色模式转换"><i class="fas fa-adjust"></i></button><button id="hide-aside-btn" type="button" title="单栏和双栏切换"><i class="fas fa-arrows-alt-h"></i></button></div><div id="rightside-config-show"><button id="rightside_config" type="button" title="设置"><i class="fas fa-cog fa-spin"></i></button><button class="close" id="mobile-toc-button" type="button" title="目录"><i class="fas fa-list-ul"></i></button><button id="go-up" type="button" title="回到顶部"><i class="fas fa-arrow-up"></i></button></div></div><div id="local-search"><div class="search-dialog"><nav class="search-nav"><span class="search-dialog-title">搜索</span><span id="loading-status"></span><button class="search-close-button"><i class="fas fa-times"></i></button></nav><div class="is-center" id="loading-database"><i class="fas fa-spinner fa-pulse"></i><span>  数据库加载中</span></div><div class="search-wrap"><div id="local-search-input"><div class="local-search-box"><input class="local-search-box--input" placeholder="搜索文章" type="text"/></div></div><hr/><div id="local-search-results"></div></div></div><div id="search-mask"></div></div><div><script src="/js/utils.js"></script><script src="/js/main.js"></script><script src="https://cdn.jsdelivr.net/npm/@fancyapps/ui/dist/fancybox.umd.min.js"></script><script src="https://cdn.jsdelivr.net/npm/node-snackbar/dist/snackbar.min.js"></script><script src="/js/search/local-search.js"></script><div class="js-pjax"></div><script src="/js/categories.js?v1"></script><script src="/js/navigation.js?v1"></script><script async data-pjax src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script></div></body></html>